home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / xulrunner / python / frontend_implementation / UIBackendDelegate.py < prev    next >
Encoding:
Python Source  |  2007-11-12  |  14.6 KB  |  352 lines

  1. # Miro - an RSS based video player application
  2. # Copyright (C) 2005-2007 Participatory Culture Foundation
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  17.  
  18. import os
  19. import logging
  20. import subprocess
  21. import resources
  22. import webbrowser
  23. import _winreg
  24. import traceback
  25. import ctypes
  26. from gtcache import gettext as _
  27. from urlparse import urlparse
  28.  
  29. import prefs
  30. import config
  31. import dialogs
  32. import feed
  33. import frontend
  34. import clipboard
  35. import urlcallbacks
  36. import util
  37.  
  38. currentId = 1
  39. def nextDialogId():
  40.     global currentId
  41.     rv = currentId
  42.     currentId += 1
  43.     return rv
  44.  
  45. def getPrefillText(dialog):
  46.     if dialog.fillWithClipboardURL:
  47.         text = clipboard.getText()
  48.         if text is not None:
  49.             text = feed.normalizeFeedURL(text)
  50.             if text is not None and feed.validateFeedURL(text):
  51.                 return text
  52.     if dialog.prefillCallback:
  53.         text = dialog.prefillCallback()
  54.         if text is not None:
  55.             return text
  56.     return ''
  57.  
  58. def _makeSupportsArrayFromSecondElement(data):
  59.     from xpcom import components
  60.     arrayAbs = components.classes["@mozilla.org/supports-array;1"].createInstance();
  61.     array = arrayAbs.queryInterface(components.interfaces.nsISupportsArray)
  62.     for datum in data:
  63.         supportsStringAbs = components.classes["@mozilla.org/supports-string;1"].createInstance();
  64.         supportsString = supportsStringAbs.queryInterface(components.interfaces.nsISupportsString)
  65.         supportsString.data = datum[1]
  66.         array.AppendElement(supportsString)
  67.     return array
  68.  
  69. class UpdateAvailableDialog(dialogs.Dialog):
  70.     """Give the user a choice of 2 options (Yes/No, Ok/Cancel,
  71.     Migrate/Don't Migrate, etc.)
  72.     """
  73.  
  74.     def __init__(self, releaseNotes):
  75.         # let the dialog load the release note page
  76.         chromeURL = "chrome://dtv/content/update_available_dialog.xul"
  77.         urlcallbacks.installCallback(chromeURL, self.urlCallback)
  78.         title = _("Update Available")
  79.         description = _("A new version of %s is available for download.") % (config.get(prefs.LONG_APP_NAME))
  80.         self.releaseNotes = releaseNotes
  81.         super(UpdateAvailableDialog, self).__init__(title, description,
  82.                 [dialogs.BUTTON_DOWNLOAD, dialogs.BUTTON_NOT_NOW])
  83.  
  84.     def urlCallback(self, url):
  85.         return True
  86.  
  87. class UIBackendDelegate:
  88.     openDialogs = {}
  89.     currentMenuItems = None
  90.  
  91.     def performStartupTasks(self, terminationCallback):
  92.         terminationCallback(None)
  93.  
  94.     def showContextMenu(self, menuItems):
  95.         UIBackendDelegate.currentMenuItems = menuItems
  96.         def getLabelString(menuItem):
  97.             if menuItem.callback is not None or menuItem.label == '':
  98.                 return menuItem.label
  99.             else:
  100.                 # hack to tell xul code that this item is disabled
  101.                 return "_" + menuItem.label 
  102.         menuString = '\n'.join([getLabelString(m) for m in menuItems])
  103.         frontend.jsBridge.showContextMenu(menuString)
  104.  
  105.     def maximizeWindow(self):
  106.         frontend.jsBridge.maximizeWindow()
  107.  
  108.     def runDialog(self, dialog):
  109.         id = nextDialogId()
  110.         self.openDialogs[id] = dialog
  111.         if isinstance(dialog, dialogs.ChoiceDialog):
  112.             frontend.jsBridge.showChoiceDialog(id, dialog.title,
  113.                     dialog.description, dialog.buttons[0].text,
  114.                     dialog.buttons[1].text)
  115.         elif isinstance(dialog, dialogs.CheckboxTextboxDialog):
  116.             frontend.jsBridge.showCheckboxTextboxDialog(id, dialog.title,
  117.                     dialog.description, dialog.buttons[0].text,
  118.                     dialog.buttons[1].text, dialog.checkbox_text, 
  119.                     dialog.checkbox_value, dialog.textbox_value)
  120.         elif isinstance(dialog, dialogs.CheckboxDialog):
  121.             frontend.jsBridge.showCheckboxDialog(id, dialog.title,
  122.                     dialog.description, dialog.buttons[0].text,
  123.                     dialog.buttons[1].text, dialog.checkbox_text, 
  124.                     dialog.checkbox_value)
  125.         elif isinstance(dialog, dialogs.ThreeChoiceDialog):
  126.             frontend.jsBridge.showThreeChoiceDialog(id, dialog.title,
  127.                     dialog.description, dialog.buttons[0].text,
  128.                     dialog.buttons[1].text, dialog.buttons[2].text)
  129.         elif isinstance(dialog, dialogs.MessageBoxDialog):
  130.             frontend.jsBridge.showMessageBoxDialog(id, dialog.title,
  131.                     dialog.description)
  132.         elif isinstance(dialog, dialogs.HTTPAuthDialog):
  133.             frontend.jsBridge.showHTTPAuthDialog(id, dialog.description)
  134.         elif isinstance(dialog, dialogs.TextEntryDialog):
  135.             frontend.jsBridge.showTextEntryDialog(id, dialog.title,
  136.                     dialog.description, dialog.buttons[0].text,
  137.                     dialog.buttons[1].text, getPrefillText(dialog))
  138.         elif isinstance(dialog, UpdateAvailableDialog):
  139.             frontend.jsBridge.showUpdateAvailableDialog(id, dialog.title,
  140.                     dialog.description, dialog.buttons[0].text,
  141.                     dialog.buttons[1].text, dialog.releaseNotes)
  142.         elif isinstance(dialog, dialogs.SearchChannelDialog):
  143.             engines = _makeSupportsArrayFromSecondElement(dialog.engines)
  144.             channels = _makeSupportsArrayFromSecondElement(dialog.channels)
  145.             defaultTerm = ""
  146.             if len(dialog.channels) > 0:
  147.                 defaultChannelId = dialog.channels[0][0]
  148.             else:
  149.                 defaultChannelId = 0
  150.             defaultEngineName = dialog.defaultEngine
  151.             defaultURL = ""
  152.             if dialog.term:
  153.                 defaultTerm = dialog.term
  154.             if dialog.style == dialog.CHANNEL:
  155.                 if dialog.location is not None:
  156.                     defaultChannelId = dialog.location
  157.             elif dialog.style == dialog.ENGINE:
  158.                 if dialog.location is not None:
  159.                     defaultEngineName = dialog.location
  160.             elif dialog.style == dialog.URL:
  161.                 if dialog.location is not None:
  162.                     defaultURL = dialog.location
  163.             defaultChannel = 0
  164.             defaultEngine = 0
  165.             for i in xrange (len (dialog.channels)):
  166.                 if dialog.channels[i][0] == defaultChannelId:
  167.                     defaultChannel = i
  168.                     break
  169.             for i in xrange (len (dialog.engines)):
  170.                 if dialog.engines[i][0] == defaultEngineName:
  171.                     defaultEngine = i
  172.                     break
  173.             frontend.jsBridge.showSearchChannelDialog(id, channels, engines, defaultTerm, dialog.style, defaultChannel, defaultEngine, defaultURL)
  174.         else:
  175.             del self.openDialogs[id]
  176.             dialog.runCallback(None)
  177.  
  178.     def askForOpenPathname(self, title, callback, defaultDirectory=None, 
  179.             typeString=None, types=None):
  180.         id = nextDialogId()
  181.         self.openDialogs[id] = callback
  182.         frontend.jsBridge.showOpenDialog(id, title, defaultDirectory,
  183.                 typeString, types)
  184.  
  185.     def askForSavePathname(self, title, callback, defaultDirectory=None, 
  186.             defaultFilename=None):
  187.         id = nextDialogId()
  188.         self.openDialogs[id] = callback
  189.         frontend.jsBridge.showSaveDialog(id, title, defaultDirectory,
  190.                 defaultFilename)
  191.  
  192.     def handleFileDialog(self, dialogID, pathname):
  193.         try:
  194.             callback = self.openDialogs.pop(dialogID)
  195.         except KeyError:
  196.             return
  197.         util.trapCall('File dialog callback', callback, pathname)
  198.  
  199.     def handleContextMenu(self, index):
  200.         self.currentMenuItems[index].activate()
  201.  
  202.     def handleDialog(self, dialogID, buttonIndex, *args, **kwargs):
  203.         try:
  204.             dialog = self.openDialogs.pop(dialogID)
  205.         except KeyError:
  206.             return
  207.         if buttonIndex is not None:
  208.             choice = dialog.buttons[buttonIndex]
  209.         else:
  210.             choice = None
  211.         if isinstance (dialog, dialogs.SearchChannelDialog):
  212.             dialog.term = kwargs['term']
  213.             dialog.style = kwargs['style']
  214.             if choice == dialogs.BUTTON_CREATE_CHANNEL:
  215.                 try:
  216.                     if dialog.style == dialog.CHANNEL:
  217.                         dialog.location = dialog.channels[int(kwargs['loc'])][0]
  218.                     elif dialog.style == dialog.ENGINE:
  219.                         dialog.location = dialog.engines[int(kwargs['loc'])][0]
  220.                     elif dialog.style == dialog.URL:
  221.                         dialog.location = kwargs['loc']
  222.                 except:
  223.                     choice = dialogs.BUTTON_CANCEL
  224.             kwargs = {}
  225.         dialog.runCallback(choice, *args, **kwargs)
  226.  
  227.     def openExternalURL(self, url):
  228.         # It looks like the maximum URL length is about 2k. I can't
  229.         # seem to find the exact value
  230.         if len(url) > 2047:
  231.             url = url[:2047]
  232.         try:
  233.             webbrowser.get("windows-default").open_new(url)
  234.         except:
  235.             print "WARNING: Error opening URL: %r" % url
  236.             traceback.print_exc()
  237.             recommendURL = config.get(prefs.RECOMMEND_URL)
  238.  
  239.             if url.startswith(config.get(prefs.VIDEOBOMB_URL)):
  240.                 title = _('Error Bombing Item')
  241.             elif url.startswith(recommendURL):
  242.                 title = _('Error Recommending Item')
  243.             else:
  244.                 title = _("Error Opening Website")
  245.  
  246.             scheme, host, path, params, query, fragment = urlparse(url)
  247.             shortURL = '%s:%s%s' % (scheme, host, path)
  248.             msg = _("There was an error opening %s.  Please try again in "
  249.                     "a few seconds") % shortURL
  250.             dialogs.MessageBoxDialog(title, msg).run()
  251.  
  252.     def revealFile (self, filename):
  253.         os.startfile(os.path.dirname(filename))
  254.  
  255.     def notifyDownloadCompleted(self, item):
  256.         pass
  257.  
  258.     def notifyDownloadFailed(self, item):
  259.         pass
  260.  
  261.     def updateAvailableItemsCountFeedback(self, count):
  262.         # Inform the user in a way or another that newly available items are
  263.         # available
  264.         # FIXME: When we have a system tray icon, remove that
  265.         pass
  266.  
  267.     def notifyUnkownErrorOccurence(self, when, log = ''):
  268.         if config.get(prefs.SHOW_ERROR_DIALOG):
  269.             frontend.jsBridge.showBugReportDialog(when, log)
  270.  
  271.     def copyTextToClipboard(self, text):
  272.         frontend.jsBridge.copyTextToClipboard(text)
  273.  
  274.     # This is windows specific right now. We don't need it on other platforms
  275.     def setRunAtStartup(self, value):
  276.         runSubkey = "Software\\Microsoft\\Windows\\CurrentVersion\\Run"
  277.         try:
  278.             folder = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, runSubkey, 0,
  279.                     _winreg.KEY_SET_VALUE)
  280.         except WindowsError, e:
  281.             if e.errno == 2: # registry key doesn't exist
  282.                 folder = _winreg.CreateKey(_winreg.HKEY_CURRENT_USER,
  283.                         runSubkey)
  284.             else:
  285.                 raise
  286.         if (value):
  287.             # We don't use the app name for the .exe, so branded
  288.             # versions work
  289.             filename = os.path.join(resources.resourceRoot(),"..","Miro.exe")
  290.             filename = os.path.normpath(filename)
  291.             themeName = config.get(prefs.THEME_NAME)
  292.             if themeName is not None:
  293.                 filename = "%s --theme \"%s\"" % (filename, themeName.replace("\\","\\\\").replace('"','\\"'))
  294.             _winreg.SetValueEx(folder, config.get(prefs.LONG_APP_NAME), 0,_winreg.REG_SZ, filename)
  295.         else:
  296.             try:
  297.                 _winreg.DeleteValue(folder, config.get(prefs.LONG_APP_NAME))
  298.             except WindowsError, e:
  299.                 if e.errno == 2: 
  300.                     # registry key doesn't exist, user must have deleted it
  301.                     # manual
  302.                     pass
  303.                 else:
  304.                     raise
  305.  
  306.     def killProcess(self, pid):
  307.         # Kill the old process, if it exists
  308.         if pid is not None:
  309.             # This isn't guaranteed to kill the process, but it's likely the
  310.             # best we can do
  311.             # See http://support.microsoft.com/kb/q178893/
  312.             # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/347462
  313.             PROCESS_TERMINATE = 1
  314.             handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, pid)
  315.             ctypes.windll.kernel32.TerminateProcess(handle, -1)
  316.             ctypes.windll.kernel32.CloseHandle(handle)
  317.  
  318.     def launchDownloadDaemon(self, oldpid, env):
  319.         self.killProcess(oldpid)
  320.         for key, value in env.items():
  321.             os.environ[key] = value
  322.         os.environ['DEMOCRACY_DOWNLOADER_LOG'] = \
  323.                 config.get(prefs.DOWNLOADER_LOG_PATHNAME)
  324.         # Start the downloader.  We use the subprocess module to turn off the
  325.         # console.  One slightly awkward thing is that the current process
  326.         # might not have a valid stdin/stdout/stderr, so we create a pipe to
  327.         # it that we never actually use.
  328.  
  329.         # Note that we use "Miro" instead of the app name here, so custom
  330.         # versions will work
  331.         downloaderPath = os.path.join(resources.resourceRoot(), "..",
  332.                                       "Miro_Downloader.exe")
  333.         startupinfo = subprocess.STARTUPINFO()
  334.         startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
  335.         subprocess.Popen(downloaderPath, stdout=subprocess.PIPE,
  336.                 stderr=subprocess.PIPE, 
  337.                 stdin=subprocess.PIPE,
  338.                 startupinfo=startupinfo)
  339.  
  340.     def handleNewUpdate(self, update_item):
  341.         url = update_item['enclosures'][0]['href']
  342.         try:
  343.             releaseNotes = update_item['description']
  344.         except:
  345.             logging.warn("Couldn't fetch release notes")
  346.             releaseNotes = ''
  347.         dialog = UpdateAvailableDialog(releaseNotes)
  348.         def callback(dialog):
  349.             if dialog.choice == dialogs.BUTTON_DOWNLOAD:
  350.                 self.openExternalURL(url)
  351.         dialog.run(callback)
  352.